package biz

import (
	"context"
	"sort"
	"strings"
	"time"

	"lianzi_gos/internal/mods/mpweixin/dal"
	"lianzi_gos/internal/mods/mpweixin/schema"
	"lianzi_gos/pkg/errors"
	"lianzi_gos/pkg/util"
)

// 分类表
type Category struct {
	Trans       *util.Trans
	CategoryDAL *dal.Category
}

// Query categories from the data access object based on the provided parameters and options.
func (a *Category) Query(ctx context.Context, params schema.CategoryQueryParam) (*schema.CategoryQueryResult, error) {
	params.Pagination = false

	result, err := a.CategoryDAL.Query(ctx, params, schema.CategoryQueryOptions{
		QueryOptions: util.QueryOptions{
			OrderFields: []util.OrderByParam{
				{Field: "sequence", Direction: util.DESC},
				{Field: "created_at", Direction: util.DESC},
			},
		},
	})
	if err != nil {
		return nil, err
	}
	result.Data = result.Data.ToTree()
	return result, nil
}
func (a *Category) appendChildren(ctx context.Context, data schema.Categories) (schema.Categories, error) {
	if len(data) == 0 {
		return data, nil
	}

	existsInData := func(id string) bool {
		for _, item := range data {
			if item.ID == id {
				return true
			}
		}
		return false
	}

	for _, item := range data {
		childResult, err := a.CategoryDAL.Query(ctx, schema.CategoryQueryParam{
			ParentPathPrefix: item.ParentPath + item.ID + util.TreePathDelimiter,
		})
		if err != nil {
			return nil, err
		}
		for _, child := range childResult.Data {
			if existsInData(child.ID) {
				continue
			}
			data = append(data, child)
		}
	}

	parentIDs := data.SplitParentIDs()
	if len(parentIDs) > 0 {
		parentResult, err := a.CategoryDAL.Query(ctx, schema.CategoryQueryParam{
			InIDs: parentIDs,
		})
		if err != nil {
			return nil, err
		}
		for _, p := range parentResult.Data {
			if existsInData(p.ID) {
				continue
			}
			data = append(data, p)
		}
		sort.Sort(data)
	}

	return data, nil
}

// Get the specified category from the data access object.
func (a *Category) Get(ctx context.Context, id string) (*schema.Category, error) {
	category, err := a.CategoryDAL.Get(ctx, id)
	if err != nil {
		return nil, err
	} else if category == nil {
		return nil, errors.NotFound("", "Category not found")
	}
	return category, nil
}

// Create a new category in the data access object.
func (a *Category) Create(ctx context.Context, formItem *schema.CategoryForm) (*schema.Category, error) {
	category := &schema.Category{
		ID:        util.NewXID(),
		CreatedAt: time.Now(),
	}
	if parentID := formItem.ParentID; parentID != "" {
		parent, err := a.CategoryDAL.Get(ctx, parentID)
		if err != nil {
			return nil, err
		} else if parent == nil {
			return nil, errors.NotFound("", "Parent not found")
		}
		category.ParentPath = parent.ParentPath + parent.ID + util.TreePathDelimiter
	}

	if err := formItem.FillTo(category); err != nil {
		return nil, err
	}

	err := a.Trans.Exec(ctx, func(ctx context.Context) error {
		if err := a.CategoryDAL.Create(ctx, category); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return category, nil
}

// Update the specified category in the data access object.
func (a *Category) Update(ctx context.Context, id string, formItem *schema.CategoryForm) error {
	category, err := a.CategoryDAL.Get(ctx, id)
	if err != nil {
		return err
	} else if category == nil {
		return errors.NotFound("", "Category not found")
	}
	oldParentPath := category.ParentPath
	oldStatus := category.Status
	var childData schema.Categories
	if category.ParentID != formItem.ParentID {
		if parentID := formItem.ParentID; parentID != "" {
			parent, err := a.CategoryDAL.Get(ctx, parentID)
			if err != nil {
				return err
			} else if parent == nil {
				return errors.NotFound("", "Parent not found")
			}
			category.ParentPath = parent.ParentPath + parent.ID + util.TreePathDelimiter
		} else {
			category.ParentPath = ""
		}

		childResult, err := a.CategoryDAL.Query(ctx, schema.CategoryQueryParam{
			ParentPathPrefix: oldParentPath + category.ID + util.TreePathDelimiter,
		}, schema.CategoryQueryOptions{
			QueryOptions: util.QueryOptions{
				SelectFields: []string{"id", "parent_path"},
			},
		})
		if err != nil {
			return err
		}
		childData = childResult.Data
	}

	if err := formItem.FillTo(category); err != nil {
		return err
	}
	category.UpdatedAt = time.Now()

	return a.Trans.Exec(ctx, func(ctx context.Context) error {
		if err := a.CategoryDAL.Update(ctx, category); err != nil {
			return err
		}
		if oldStatus != formItem.Status {
			opath := oldParentPath + category.ID + util.TreePathDelimiter
			if err := a.CategoryDAL.UpdateStatusByParentPath(ctx, opath, formItem.Status); err != nil {
				return err
			}
		}

		for _, child := range childData {
			opath := oldParentPath + category.ID + util.TreePathDelimiter
			npath := category.ParentPath + category.ID + util.TreePathDelimiter
			err := a.CategoryDAL.UpdateParentPath(ctx, child.ID, strings.Replace(child.ParentPath, opath, npath, 1))
			if err != nil {
				return err
			}
		}
		return nil
	})
}

// Delete the specified category from the data access object.
func (a *Category) Delete(ctx context.Context, id string) error {
	category, err := a.CategoryDAL.Get(ctx, id)
	if err != nil {
		return err
	} else if category == nil {
		return errors.NotFound("", "Category not found")
	}

	childResult, err := a.CategoryDAL.Query(ctx, schema.CategoryQueryParam{
		ParentPathPrefix: category.ParentPath + category.ID + util.TreePathDelimiter,
	}, schema.CategoryQueryOptions{
		QueryOptions: util.QueryOptions{
			SelectFields: []string{"id"},
		},
	})
	if err != nil {
		return err
	}

	return a.Trans.Exec(ctx, func(ctx context.Context) error {
		if err := a.CategoryDAL.Delete(ctx, id); err != nil {
			return err
		}
		for _, child := range childResult.Data {
			if err := a.CategoryDAL.Delete(ctx, child.ID); err != nil {
				return err
			}
		}
		return nil
	})
}
